# text_extractor.py
# -*- coding: utf-8 -*-

import os
import logging
import requests # 用于调用 OCR API
import docx
import fitz # PyMuPDF
from utils import read_file_content
from typing import Optional, Dict, Any
import config # 导入配置

# --- Markdown 到纯文本转换依赖 ---
try:
    import markdown_it
    import html2text
    MARKDOWN_PROCESSING_AVAILABLE = True
    # 初始化转换器 (模块级别，避免重复创建)
    md_parser_for_extractor = markdown_it.MarkdownIt()
    text_maker_for_extractor = html2text.HTML2Text()
    text_maker_for_extractor.ignore_links = True
    text_maker_for_extractor.ignore_images = True
    text_maker_for_extractor.body_width = 0
except ImportError:
    MARKDOWN_PROCESSING_AVAILABLE = False
    md_parser_for_extractor = None
    text_maker_for_extractor = None
    logging.warning(
        "text_extractor: markdown-it-py 或 html2text 未安装。"
        "如果 OCR API 返回 Markdown，将无法转换为纯文本。"
        "请运行: pip install markdown-it-py html2text"
    )

# 配置日志记录器
logger = logging.getLogger(__name__)


def _markdown_to_plain_text(markdown_text: str) -> str:
    """(内部辅助) 将 Markdown 文本转换为纯文本。"""
    if not MARKDOWN_PROCESSING_AVAILABLE:
        logger.error("Markdown 处理库不可用，无法将 Markdown 转换为纯文本。返回原始 Markdown。")
        return markdown_text # 或者返回错误提示或空字符串
    if not markdown_text or not md_parser_for_extractor or not text_maker_for_extractor:
        return ""
    try:
        html_content = md_parser_for_extractor.render(markdown_text)
        plain_text = text_maker_for_extractor.handle(html_content)
        return plain_text.strip()
    except Exception as e:
        logger.error(f"将 Markdown 转换为纯文本时出错: {e}", exc_info=True)
        return markdown_text # 出错时返回原始 Markdown，或空字符串，根据需求

def _extract_docx(file_path: str) -> Optional[str]:
    """
    从 Microsoft Word (.docx) 文件中提取纯文本内容。
    """
    logger.debug(f"尝试提取 DOCX 文件: {file_path}")
    try:
        doc = docx.Document(file_path)
        full_text = [para.text for para in doc.paragraphs]
        extracted_text = '\n'.join(full_text)
        logger.info(f"成功提取 DOCX 文件: {os.path.basename(file_path)}, 字符数: {len(extracted_text)}")
        return extracted_text
    except docx.opc.exceptions.PackageNotFoundError:
        logger.error(f"无法找到 DOCX 文件或文件已损坏: {file_path}", exc_info=True)
        return None
    except Exception as e:
        logger.error(f"提取 DOCX 文件时发生未知错误: {file_path}, 错误: {e}", exc_info=True)
        return None

def _call_ocr_api(file_path: str) -> Optional[str]:
    """
    通过 API 调用外部 OCR 服务处理 PDF 文件，并将其返回的 Markdown 转换为纯文本。
    """
    if not config.INTERNAL_PDF_OCR_RETRY_ENDPOINT:
        logger.warning("OCR API 端点未配置 (config.INTERNAL_PDF_OCR_RETRY_ENDPOINT)，无法调用 OCR 服务。")
        return None

    logger.info(f"尝试使用外部 OCR API ({config.INTERNAL_PDF_OCR_RETRY_ENDPOINT}) 处理文件: {file_path}")
    try:
        with open(file_path, 'rb') as f:
            files = {'file': (os.path.basename(file_path), f, 'application/pdf')}
            response = requests.post(
                config.INTERNAL_PDF_OCR_RETRY_ENDPOINT,
                files=files,
                timeout=config.OCR_API_TIMEOUT
            )
        response.raise_for_status()
        
        response_json = response.json()
        # 根据 mineru_api.py，我们期望 "markdown_content" 字段
        if response_json.get("success") and "markdown_content" in response_json:
            markdown_text = response_json["markdown_content"]
            if markdown_text is None: # API 可能返回 null
                logger.warning(f"OCR API 成功处理但返回的 markdown_content 为 null: {os.path.basename(file_path)}")
                return "" # 返回空字符串表示内容为空

            logger.info(f"OCR API 成功处理并返回 Markdown: {os.path.basename(file_path)}, Markdown 长度: {len(markdown_text)}")
            
            # 将 Markdown 转换为纯文本
            plain_text = _markdown_to_plain_text(markdown_text)
            logger.info(f"Markdown 转换为纯文本完成, 纯文本长度: {len(plain_text)}")
            return plain_text
        else:
            error_msg = response_json.get("error", "OCR API 返回成功但缺少 markdown_content 或指示失败")
            logger.error(f"OCR API 处理文件 {os.path.basename(file_path)} 逻辑失败: {error_msg}")
            return None

    except requests.exceptions.Timeout:
        logger.error(f"调用 OCR API 超时 ({config.OCR_API_TIMEOUT}秒): {file_path}", exc_info=True)
        return None
    except requests.exceptions.RequestException as e:
        logger.error(f"调用 OCR API 时发生网络或请求错误: {file_path}, 错误: {e}", exc_info=True)
        return None
    except ValueError as e: # requests.json() 可能抛出
        logger.error(f"解析 OCR API 响应 JSON 失败: {file_path}, 响应状态: {response.status_code}, 响应内容: {response.text[:200]}..., 错误: {e}", exc_info=True)
        return None
    except Exception as e:
        logger.error(f"调用 OCR API 或处理其响应时发生未知错误: {file_path}, 错误: {e}", exc_info=True)
        return None


def _extract_pdf(file_path: str) -> Optional[str]:
    """
    从 PDF (.pdf) 文件中提取纯文本内容。
    首先尝试 PyMuPDF，如果效果不佳或配置了 OCR，则调用外部 OCR API。
    """
    logger.debug(f"尝试使用 PyMuPDF (fitz) 提取 PDF 文件: {file_path}")
    extracted_text_pymupdf: Optional[str] = None
    try:
        doc = fitz.open(file_path)
        text_parts = []
        for page_num in range(len(doc)):
            page = doc.load_page(page_num)
            page_text = page.get_text("text")
            text_parts.append(page_text)
        doc.close()
        raw_pymupdf_text = "".join(text_parts)
        
        # 对 PyMuPDF 提取的文本也进行一次基础的清理和 strip
        # 因为 PyMuPDF 可能提取出很多不必要的空格或控制字符
        if raw_pymupdf_text:
            # 简单的 strip 和多余空白压缩，更复杂的清洗交给 preprocessor
            temp_cleaned_pymupdf = " ".join(raw_pymupdf_text.split())
            if temp_cleaned_pymupdf:
                 extracted_text_pymupdf = temp_cleaned_pymupdf
                 logger.info(f"PyMuPDF 提取 PDF 成功: {os.path.basename(file_path)}, 初步清理后字符数: {len(extracted_text_pymupdf)}")
            else:
                logger.warning(f"使用 PyMuPDF 从 PDF 提取的文本经初步清理后为空: {file_path}")
                extracted_text_pymupdf = None
        else:
            logger.warning(f"使用 PyMuPDF 从 PDF 提取的原始文本即为空: {file_path}")
            extracted_text_pymupdf = None

    except FileNotFoundError:
         logger.error(f"PDF 文件未找到: {file_path}", exc_info=True)
         return None
    except fitz.fitz.FileDataError as e: # PyMuPDF 特定的文件数据错误
         logger.error(f"PyMuPDF 无法打开或读取 PDF 文件 (可能已损坏或受密码保护): {file_path}, 错误: {e}", exc_info=True)
         extracted_text_pymupdf = None
    except Exception as e:
        logger.error(f"使用 PyMuPDF 提取 PDF 文件时发生未知错误: {file_path}, 错误: {e}", exc_info=True)
        extracted_text_pymupdf = None

    use_ocr_fallback = False
    if config.USE_OCR_FOR_PDF:
        if extracted_text_pymupdf is None:
            logger.info(f"PyMuPDF 未能提取任何有效文本，将尝试使用 OCR: {file_path}")
            use_ocr_fallback = True
        # strip() 后的长度判断
        elif len(extracted_text_pymupdf.strip()) < config.MIN_TEXT_LENGTH_FOR_NON_OCR_PDF:
            logger.info(f"PyMuPDF 提取文本过短 (strip后 {len(extracted_text_pymupdf.strip())} < {config.MIN_TEXT_LENGTH_FOR_NON_OCR_PDF})，尝试 OCR: {file_path}")
            use_ocr_fallback = True
    
    if use_ocr_fallback:
        ocr_result_plain_text = _call_ocr_api(file_path)
        if ocr_result_plain_text is not None: # 即使 OCR 返回空字符串，也认为是 OCR 的结果
            return ocr_result_plain_text
        else:
            logger.warning(f"OCR 处理失败或未返回有效文本，将回退到 PyMuPDF 的结果 (如果有): {file_path}")
            # 注意：如果 extracted_text_pymupdf 是 None，这里也会返回 None
            return extracted_text_pymupdf 
    else:
        return extracted_text_pymupdf


def _extract_txt(file_path: str) -> Optional[str]:
    """
    从纯文本 (.txt) 文件中提取内容。
    """
    logger.debug(f"尝试提取 TXT 文件: {file_path}")
    content = read_file_content(file_path)
    if content is not None:
        logger.info(f"成功提取 TXT 文件: {os.path.basename(file_path)}, 字符数: {len(content)}")
    else:
        logger.warning(f"未能从 TXT 文件提取内容: {file_path}")
    return content


def extract_text(file_path: str) -> Optional[str]:
    """
    根据文件扩展名，选择合适的提取器从文件中提取文本内容。
    返回提取的纯文本，或者在提取失败时返回 None，如果提取成功但内容为空白则返回空字符串。
    """
    if not os.path.exists(file_path):
        logger.error(f"文件不存在，无法提取: {file_path}")
        return None
    if not os.path.isfile(file_path):
        logger.error(f"提供的路径不是一个有效的文件，无法提取: {file_path}")
        return None

    _, file_extension = os.path.splitext(file_path)
    file_extension = file_extension.lower()
    filename_basename = os.path.basename(file_path)
    logger.info(f"开始提取文件: {filename_basename} (类型: {file_extension})")

    extracted_text: Optional[str] = None
    if file_extension == '.docx':
        extracted_text = _extract_docx(file_path)
    elif file_extension == '.pdf':
        extracted_text = _extract_pdf(file_path)
    elif file_extension == '.txt':
        extracted_text = _extract_txt(file_path)
    else:
        logger.warning(f"不支持的文件类型: {file_extension}。文件: {file_path}")
        return None # 对于不支持的类型，明确返回 None

    if extracted_text is None:
        logger.warning(f"未能从文件提取任何有效文本: {filename_basename} (类型: {file_extension})")
        return None
    elif not extracted_text.strip(): # 检查是否只有空白字符
         logger.warning(f"从文件提取的文本为空或仅包含空白字符: {filename_basename} (类型: {file_extension})")
         # 对于成功提取但内容为空白的情况，返回空字符串，而不是 None
         # 后续的 preprocessor.clean_text("") 会返回 ""
         return "" 
    
    logger.info(f"文件提取完成: {filename_basename}, 共提取 {len(extracted_text)} 个字符（包括空白，未最终清洗）。")
    return extracted_text
